"""
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
* Neither the names of the authers, nor the names of its contributors, may be used to endorse or 
promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE REGENTS/COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
REGENTS/COPYRIGHT OWNERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@Author Fredrique Samuels <fredriquesamuels@gmail.com>
@anim8.py  
"""
from math import *
import time
import types

#=============================================================================

def ANIMATE_LINEAR(start_value,end_value,aniMan,current_value):
    """Calculate the progress of a variable animated linearly 
    from start_value up to end_value given the current value
    
    
    @param start_value: The value the number started on
    @param end_value: The target value 
    @param aniMan: Animation Manager
    @param current_value: Current value of the number
    @return: New value  
    """
    if(current_value == end_value):
        return current_value
    if (start_value < end_value):
        if( current_value >= end_value ):
            return current_value   
    else:
        if( current_value < end_value ):
            return current_value
           
    current_value = start_value + ( aniMan.getAnimationProgress() * (end_value - start_value) );
    return current_value;

#=============================================================================

def ANIMATE_EXP(start_value,end_value,aniMan,current_value):
    """Calculate the progress of a variable animated exponensally
    from start_value up to end_value given the current value
    
    
    @param start_value: The value the number started on
    @param end_value: The target value 
    @param aniMan: Animation Manager
    @param current_value: Current value of the number
    @return: New value  
    """
    
    prog = aniMan.getAnimationProgress()
    if( prog >= 1.0 ):
        return end_value
		
    # when animation progress = 1, the current value is 99.9% of the end_value 
    current_value = start_value+(1-exp(-(7*prog))) * (end_value - start_value);
    return current_value;

#=============================================================================

def ANIMATE_WOB(start_value,end_value,aniMan,current_value):
    """Calculate the progress of a variable animated using a 
    damping sine wave from start_value up to end_value given 
    the current value
    
    @param start_value: The value the number started on
    @param end_value: The target value 
    @param aniMan: Animation Manager
    @param current_value: Current value of the number
    @return: New value  
    """
    prog = aniMan.getAnimationProgress()
    if( prog >= 1.0 ):
        return end_value

    # values carefully selected to animate the wobble and finish
    # when the animation progress reaches 100% (1)
    exp_neg_t5 = exp(-(5*prog));
    current_value = (1-exp_neg_t5)+1.4*(-exp_neg_t5*sin(prog*10));
    return current_value;
	
#=============================================================================
    
class pytTimeAnimation:
    """ Class for managing animation progress """
    
    
    def __init__(self):
        """
        Constructor
        """
        self.__startTime_ = 0
        self.__endTime_ = 0
        self.__durTime_ = 500
        self.__done_ = False
    
    def getAnimationProgress(self):
        """Get the animation progress.
        
        @return: Progress of the animation where 
        0.0 is the start and 1.0 is the end of the 
        animation.
        """
        now = time.time()*1000
   
        if self.__durTime_ == 0:
            self.__done_ = True
            return 1
        
        if now >= self.__endTime_:
            self.__done_ = True
            return 1
        self.__done_ = False
        return float( now - self.__startTime_ ) / self.__durTime_ 

    def setAnimationDuration(self,dur):
        """ Set how long the animation should last.
        
        @param dur: Duration in milliseconds 
        """
        if dur < 0:
            return 
        self.__durTime_ = dur;
        self.reset()

    def reset(self):
        """Reset the time. This will stop any animation
        managed by this timer instance.
        """
        self.__startTime_ =  time.time()*1000
        self.__endTime_ = self.__startTime_ + self.__durTime_
        self.__done_ = False
    
    def isRunning(self):
        """ Indicated if the timer is still running.
        
        @return: False if getAnimationProgress() returns 1.0 
        else True is returned
        """
        return not (self.getAnimationProgress() == 1.0)
    
#=============================================================================

class pytAniVar(pytTimeAnimation):
    """ Basic variable class for animating a value """
    
    def __init__(self,val = 0):
        """ Constructor """
        pytTimeAnimation.__init__(self)
        self.__startVal_ = val
        self.__endVal_ = val
        self.__curVal_ = val
        self.__ani8or_ = ANIMATE_LINEAR

    def getValue(self):
        """ Get the current value
        
        @return: Value stored by this objact 
        """
        self.__curVal_ = self.__ani8or_(self.__startVal_,self.__endVal_,self,self.__curVal_)
        return float(self.__curVal_)
        
    def setValue(self,val):
        """ Set the current value. Calling this
        stops any animation being done on this 
        variable
        
        @param val: New value 
        """
        self.__endVal_ = val
        self.__curVal_ = val

    def setAnimateType(self,ani):
        """ Set the animation type.
        Typical values are ANIMATE_LINEAR,ANIMATE_EXP,ANIMATE_WOB
        
        @param ani: Type of animation
        """
        self.__ani8or_ = ani

    def setTargetValue(self,val,time = -1):
        """ Set the target value of this instance.
        This will cause the value to animate to 
        the given value in the time specified in milliseconds.
        
        @param val: Target value
        @param time: time in milliseconds. Defaults to 500  
        """
        self.__startVal_ = self.__curVal_
        self.__endVal_ = val
        self.__curVal_ = self.__curVal_
        self.setAnimationDuration(time)
        self.reset()
        
    def add(self,v):
        """ Add a value to the current value. 
        This method calls setValue().
        
        @param v: Value to add
        @see setValue(self,v)
        """
        self.setValue(self.getValue()+v)
        
    def subtract(self,v):
        """ Subtracts a value from the current value. 
        This method calls setValue().
        
        @param v: Value to subtract
        @see setValue(self,v)
        """
        self.setValue(self.getValue()-v)
        
    def divide(self,v):
        """ Devides the current value. 
        This method calls setValue().
        
        @param v: Value to divide by
        @see setValue(self,v)
        """
        self.setValue(self.getValue()/float(v))
        
    def multiply(self,v):
        """ Multiplies the current value. 
        This method calls setValue().
        
        @param v: Value to multiply by
        @see setValue(self,v)
        """
        self.setValue(self.getValue()*v)
        
#=======================================================================================

class pytAniVector:
    """ Vector that can be used for animation """
    
    
    def __init__(self,x = 0,y = 0 ,z = 0):
        """ Class Constructor 
        
        @param x: X value. Defualts to 0
        @param y: Y value. Defualts to 0
        @param z: Z value. Defualts to 0
        """
        self.__x = pytAniVar(x)
        self.__y = pytAniVar(y)
        self.__z = pytAniVar(z)
        
    def setTargetValue(self,x,y,z,time = -1):
        """ Set the target value of this vector.
        This will cause the value to animate to 
        the given value in the time specified in milliseconds.
        
        @param x: X value.
        @param y: Y value. 
        @param z: Z value. 
        @param time: time in milliseconds. Defaults to 500 
        """
        self.__x.setTargetValue(x,time)
        self.__y.setTargetValue(y,time)
        self.__z.setTargetValue(z,time)

    def setValue(self,x,y,z):
        """ Set the value of this vector.
    
        @param x: X value.
        @param y: Y value. 
        @param z: Z value. 
        """
        self.__x.setValue(x)
        self.__y.setValue(y)
        self.__z.setValue(z)

    def getValue(self):
        """ Return value of this vector
        
        return: Vector value (x,y,z)
        """
        return (self.__x.getValue(),self.__y.getValue(),self.__z.getValue())

    def setX(self,x):
        """ Set the X value
        
        @param x: X value
        """
        self.__x.setValue(x)

    def setY(self,y):
        """ Set the Y value
        
        @param y: Y value
        """
        self.__y.setValue(y)

    def setZ(self,z):
        """ Set the Z value
        
        @param z: Z value
        """
        self.__z.setValue(z)

    def getX(self):
        """ Get the X value
        
        @return: X value
        """
        return self.__x.getValue()

    def getY(self):
        """ Get the Y value
        
        @return: Y value
        """
        return self.__y.getValue()

    def getZ(self):
        """ Get the Z value
        
        @return: Z value
        """
        return self.__z.getValue()

    def setTargetX(self,v,time = -1):
        """ Set the target value of this X component.
        This will cause the value to animate to 
        the given value in the time specified in milliseconds.
        
        @param v: X value. 
        @param time: time in milliseconds. Defaults to 500 
        """
        self.__x.setTargetValue(v,time)

    def setTargetY(self,v,time = -1):
        """ Set the target value of this Y component.
        This will cause the value to animate to 
        the given value in the time specified in milliseconds.
        
        @param v: Y value. 
        @param time: time in milliseconds. Defaults to 500 
        """
        self.__y.setTargetValue(v,time)

    def setTargetZ(self,v,time = -1):
        """ Set the target value of this Z component.
        This will cause the value to animate to 
        the given value in the time specified in milliseconds.
        
        @param v: Z value. 
        @param time: time in milliseconds. Defaults to 500 
        """
        self.__z.setTargetValue(v,time)

    def setAnimateType(self,ani):
        """ Set the animation type.
        Typical values are ANIMATE_LINEAR,ANIMATE_EXP,ANIMATE_WOB
        
        @param ani: Type of animation
        """
        self.__x.setAnimateType(ani)
        self.__y.setAnimateType(ani)
        self.__z.setAnimateType(ani)

    def setAnimationDuration(self,arg0):
        """ Set how long the animation should last.
        
        @param dur: Duration in milliseconds 
        """
        self.__x.setAnimationDuration(arg0)
        self.__y.setAnimationDuration(arg0)
        self.__z.setAnimationDuration(arg0)

    def isRunning(self):
        """ 
        @see pytTimeAnimation.isRunning()
        """
        return  self.__x.isRunning() or self.__y.isRunning() or self.__z.isRunning()
    
    def add(self,v):
        """ Add a value to this vector. 
        If v in a tuple (vx,vy,vz). This vector's value becomes (x+vx,y+vy,z+vz).
        If v is a single value, This vector's value becomes (x+v,y+v,z+v).
        
        @param v: Value
        """
        if isinstance(v, types.IntType):
            cur = self.getValue()
            self.setValue( cur[0]+v ,cur[1]+v ,cur[2]+v)
            return
        elif isinstance(v, types.TupleType):
            cur = self.getValue()
            self.setValue( cur[0]+v[0] ,cur[1]+v[1] ,cur[2]+v[2])
            return
        
        raise TypeError(" v must be instance of TupleType or IntType not "+str(type(v)))
    
    def subtract(self,v):
        """ Subtract a value from this vector. 
        If v in a tuple (vx,vy,vz). This vector's value becomes (x-vx,y-vy,z-vz).
        If v is a single value, This vector's value becomes (x-v,y-v,z-v).
        
        @param v: Value
        """
        if isinstance(v, types.IntType):
            cur = self.getValue()
            self.setValue( cur[0]-v ,cur[1]-v ,cur[2]-v)
            return
        elif isinstance(v, types.TupleType):
            cur = self.getValue()
            self.setValue( cur[0]-v[0] ,cur[1]-v[1] ,cur[2]-v[2])
            return
        
        raise TypeError(" v must be instance of TupleType or IntType not "+str(type(v)))
    
    def multiply(self,v):
        """ Multiply a value to this vector. 
        If v in a tuple (vx,vy,vz). This vector's value becomes (x*vx,y*vy,z*vz).
        If v is a single value, This vector's value becomes (x*v,y*v,z*v).
        
        @param v: Value
        """
        if isinstance(v, types.IntType):
            cur = self.getValue()
            self.setValue( cur[0]*v ,cur[1]*v ,cur[2]*v)
            return
        elif isinstance(v, types.TupleType):
            cur = self.getValue()
            self.setValue( cur[0]*v[0] ,cur[1]*v[1] ,cur[2]*v[2])
            return
        
        raise TypeError(" v must be instance of TupleType or IntType not "+str(type(v)))
    
    def devide(self,v):
        """ Multiply a value to this vector. 
        If v in a tuple (vx,vy,vz). This vector's value becomes (x/vx,y/vy,z/vz).
        If v is a single value, This vector's value becomes (x/v,y/v,z/v).
        
        @param v: Value
        """
        if isinstance(v, types.IntType):
            cur = self.getValue()
            self.setValue( cur[0]/float(v) ,cur[1]/float(v) ,cur[2]/float(v))
            return
        elif isinstance(v, types.TupleType):
            cur = self.getValue()
            self.setValue( cur[0]/float(v[0]),cur[1]/float(v[1]) ,cur[2]/float(v[2]))
            return
        
        raise TypeError(" v must be instance of TupleType or IntType not "+str(type(v)))
    
    def __str__(self):
        """ String type overload """
        v = self.getValue()
        ret= ''
        ret+='pytAniVector x='
        ret+=str(v[0])
        ret+=' y='
        ret+=str(v[1])
        ret+=' z='
        ret+=str(v[2])
        return ret
            
    
#        
#def main():
#    ani = ANIMATE_LINEAR
#    ta = pytAniVar()
#
#    s = 0
#    e = 15
#    c = 0
#    ta.setAnimationDuration(5000)
#    ta.setAnimateType(ani)
#    ta.setTargetValue(15)
#    while not ta.isRunning():
#        c = ta.getValue()
#        print c
#
#    vec = pytAniVector()
#    print vec.getValue()
#    vec.setAnimateType(ani)
#    vec.setTargetValue(15,15,15)
#
#    while not vec.isRunning():
#        c = vec.getValue()
#        print c
#    
#if __name__ == '__main__':
#    v = pytAniVector()
#    v.add([2,3,4])
#    print v
#    main()

